package com.whz.generic.crypt.aes;

import com.whz.generic.uuid.PasswordGenerator;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * 基于AES/CBC/PKCS5Padding的加密工具,支持16,24,32字节的秘钥
 *
 * @author whz
 */
@Slf4j
@UtilityClass
public class AesUtil {
    private final static String AES = "AES";
    /**
     * 定义一个16byte的初始向量
     */
    private static final String IV_STRING = "3LVJr8tPySA1s3I7";
    private static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding";


    /**
     * 生成16B的随机密码,含有数字,大小写字符和特殊字符
     *
     * @return 16个字符的密码
     */
    public static String generate16ByteSecretKey() {
        return generateSecretKey(16);
    }

    /**
     * 生成24B的随机密码,含有数字,大小写字符和特殊字符
     *
     * @return 24个字符的密码
     */
    public static String generate24ByteSecretKey() {
        return generateSecretKey(24);
    }

    /**
     * 生成32B的随机密码,含有数字,大小写字符和特殊字符
     *
     * @return 32个字符的密码
     */
    public static String generate32ByteSecretKey() {
        return generateSecretKey(32);
    }

    /**
     * 产生一个16,24,32 byte的密钥字符串
     *
     * @return 16, 24, 32个字节的秘钥
     */
    public static String generateSecretKey(int length) {
        Assert.isTrue(length == 16 || length == 24 || length == 32, "secret key length is [16,24,32]");
        PasswordGenerator passwordGenerator = new PasswordGenerator(length, 4);
        return passwordGenerator.generateRandomPassword();
    }

    /**
     * 加密
     *
     * @param content   明文
     * @param secretKey 秘钥
     * @return 密文
     */
    @SneakyThrows
    public static String encrypt(String content, String secretKey) {
        Assert.hasText(content, "content not blank");
        Assert.hasText(secretKey, "secretKey not blank");

        byte[] contentByte = getBytes(content);
        byte[] keyByte = getBytes(secretKey);
        //初始化一个密钥对象
        SecretKeySpec keySpec = new SecretKeySpec(keyByte, AES);
        //初始化一个初始向量,不传入的话，则默认用全0的初始向量
        byte[] initParam = getBytes(IV_STRING);
        IvParameterSpec ivSpec = new IvParameterSpec(initParam);
        // 指定加密的算法、工作模式和填充方式
        Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encryptedBytes = cipher.doFinal(contentByte);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    private static byte[] getBytes(String input) {
        return input.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 解密
     *
     * @param decryptContent 密文
     * @param secretKey      秘钥
     * @return 明文
     */
    @SneakyThrows
    public static String decrypt(String decryptContent, String secretKey) {
        Assert.hasText(decryptContent, "content not blank");
        Assert.hasText(secretKey, "secretKey not blank");

        byte[] contentByte = Base64.getDecoder().decode(decryptContent);
        byte[] keyByte = getBytes(secretKey);
        //初始化一个密钥对象
        SecretKeySpec keySpec = new SecretKeySpec(keyByte, AES);
        //初始化一个初始向量,不传入的话，则默认用全0的初始向量
        byte[] initParam = getBytes(IV_STRING);
        IvParameterSpec ivSpec = new IvParameterSpec(initParam);
        // 指定加密的算法、工作模式和填充方式
        Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] result = cipher.doFinal(contentByte);
        return new String(result, StandardCharsets.UTF_8);
    }
}